












"""

1. 所有接口，更新orig_amount
2. 测试多次部分成交，多次modify订单的origAmt
"""


















if __name__ == '__main__':
    import sys
    sys.path.append('../../')


import time
import json
import requests
from quant.markets import Functions
from quant.markets.functions import get_asset_value
from quant.accounts import Order
from quant.exchanges.util import (spot_value_factor, Socket, RecentChecker2, AscendingChecker, update_balance_default, prepare_placing,
                                  prepare_canceling, prepare_modifying, avoid_sci_num, simulate_deal_spot, next_cid_head)
from quant.exchanges.basics import ChannelBasic, HandlerBasic, ApiBasic, SpiBasic
from quant.exchanges.binance_util import build_request, sign_ws_param
from quant.utils import logging, Timer2, catch_exception, Iota
from quant.const import *
from quant import config


class BinanceSpotChannel(ChannelBasic):
    def init(self):
        if self.event == Event.Book:
            host = book_host(self.symbol, self.frequency)
        elif self.event == Event.Trade:
            host = trade_host(self.symbol, self.frequency)
        elif self.event == Event.Ticker:
            host = ticker_host(self.symbol, self.frequency)
        else:
            raise NotImplementedError('Event for {} not implemented'.format(self.event))

        self.socket = Socket(host, self.on_open, self.on_message, self.on_close)

    def subscribe_book(self, ws):
        return

    def subscribe_trade(self, ws):
        return

    def subscribe_ticker(self, ws):
        return


class BinanceSpotHandler(HandlerBasic):
    def init(self):
        self.data_id_checker = RecentChecker2(50)
        self.data_id_checker_2 = AscendingChecker()

    def process_book(self, routing_key, recv_time, raw):
        if raw == NAME_CHANNEL_CLOSE:
            return self.process_close(routing_key, recv_time)

        data = json.loads(raw)
        server_id = data['u']
        server_time = data['E'] / 1000
        self.markets.info_engine.push_process_recv(server_id, server_time, recv_time)

        if not self.data_id_checker.is_new(server_id):
            return

        bids = data['b']
        asks = data['a']

        book = self.book

        for p, v in bids:
            book['buy', float(p)] = float(v)
        for p, v in asks:
            book['sell', float(p)] = float(v)

        self.check_book(book)
        self.push_book(routing_key, recv_time, server_time, server_id, book)

    def process_trade(self, routing_key, recv_time, raw):
        if raw == NAME_CHANNEL_CLOSE:
            return self.process_close(routing_key, recv_time)

        data = json.loads(raw)
        server_id = data['l']
        server_time = data['E']
        self.markets.info_engine.push_process_recv(server_id, server_time, recv_time)

        if not self.data_id_checker.is_new(server_id):
            return

        trade_list = [[
            side_map[data['m']],
            float(data['p']),
            float(data['q']),
        ]]

        self.push_trade(routing_key, recv_time, server_time, server_id, trade_list)

    def process_ticker(self, routing_key, recv_time, raw):
        if raw == NAME_CHANNEL_CLOSE:
            return self.process_close(routing_key, recv_time)

        j = json.loads(raw)
        server_id = j['u']
        self.markets.info_engine.push_process_recv(server_id, None, recv_time)

        if not self.data_id_checker_2.is_new(server_id):
            return

        ticker = (
            (float(j['b']), float(j['B'])),
            (float(j['a']), float(j['A'])),
        )
        self.push_ticker(routing_key, recv_time, None, server_id, ticker)


class BinanceSpotApi(ApiBasic):
    rest_executor = None
    ws_executor = None
    executor = None

    def init(self):
        self.rest_executor = RestExecutor(self)
        self.ws_executor = None
        self.executor = self.rest_executor

    def set_ws_mode(self, ws=True):
        if ws:
            if self.ws_executor is None:
                self.ws_executor = WsExecutor(self)
            self.executor = self.ws_executor
        else:
            self.executor = self.rest_executor

    def query_balance(self, symbol=None):
        path = 'api/v3/account'
        req = build_request(rest_host, 'GET', path, self.api_key, self.secret_key)
        self.requesting.request_async(symbol, req, self._on_balance)

    def query_all_balance(self):
        path = 'api/v3/account'
        req = build_request(rest_host, 'GET', path, self.api_key, self.secret_key)
        self.requesting.request_async(None, req, self._on_all_balance)

    def query_all_position(self):
        return

    def query_all_margin(self):
        return

    def query_open_orders(self, symbol=None):
        path = 'api/v3/openOrders'
        param = {'symbol': format_symbol(symbol, True)}
        req = build_request(rest_host, 'GET', path, self.api_key, self.secret_key, param)
        self.requesting.request_async(symbol, req, self._on_open_orders)

    def query_all_open_orders(self):
        path = 'api/v3/openOrders'
        req = build_request(rest_host, 'GET', path, self.api_key, self.secret_key)
        self.requesting.request_async(None, req, self._on_all_open_orders)

    def place_order(self, order):
        return self.executor.place_order(order)

    def cancel_order(self, order):
        return self.executor.cancel_order(order)

    def modify_order(self, old, new, **options):
        return self.executor.modify_order(old, new, **options)

    def _on_balance(self, symbol, response):
        if int(response.status_code) // 100 != 2:
            return self._fail_request('query_balance', symbol, response)

        if symbol is None:
            interests = self._asset_added
        else:
            interests = {*symbol.split('/')}

        j = response.json()
        balance = self.account.balance

        for dic in j['balances']:
            asset = dic['asset'].lower()
            if asset in interests:
                update_balance_default(balance, asset, float(dic['free']), float(dic['locked']))

        self._put_data(UserEvent.Balance, balance)

    def _on_all_balance(self, _, response):
        if int(response.status_code) // 100 != 2:
            return self._fail_request('query_all_balance', _, response)

        j = response.json()
        balance = {}

        for dic in j['balances']:
            asset = dic['asset'].lower()
            free = float(dic['free'])
            frozen = float(dic['locked'])
            if free or frozen:
                update_balance_default(balance, asset, free, frozen)

        self.account.balance.clear()
        self.account.balance.update(balance)

        self._put_data(UserEvent.Balance, balance)

    def _on_open_orders(self, symbol, response):
        if int(response.status_code) // 100 != 2:
            return self._fail_request('query_open_orders', symbol, response)

        open_orders = []
        for dic in response.json():
            order = Order(
                symbol=symbol,
                side=dic['side'].lower(),
                price=float(dic['price']),
                amount=abs(float(dic['origQty']) - float(dic['executedQty'])),
                client_id=dic['clientOrderId'],  # 作为template时要注意，有的交易所不会赋值手动单的cid.
                order_id=dic['orderId'],
                status=OrderStatus.Pending,
            )
            order.orig_amount = abs(float(dic['origQty']))
            open_orders.append(order)

        self.account.order_processor.process_open_orders(open_orders)
        self._put_data(UserEvent.OpenOrders, self.account.orders)

    def _on_all_open_orders(self, _, response):
        if int(response.status_code) // 100 != 2:
            return self._fail_request('query_all_open_orders', None, response)

        open_orders = []
        for dic in response.json():
            order = Order(
                symbol=resume_symbol(dic['symbol']),
                side=dic['side'].lower(),
                price=float(dic['price']),
                amount=abs(float(dic['origQty']) - float(dic['executedQty'])),
                client_id=dic['clientOrderId'],  # 作为template时要注意，有的交易所不会赋值手动单的cid.
                order_id=dic['orderId'],
                status=OrderStatus.Pending,
            )
            order.orig_amount = abs(float(dic['origQty']))
            open_orders.append(order)

        if open_orders:
            self.account.order_processor.process_open_orders(open_orders)
            self._put_data(UserEvent.OpenOrders, self.account.orders)

    def transfer_swap(self, amount, back_to_spot=False, asset='usdt'):
        type_ = 'MAIN_UMFUTURE'
        if back_to_spot:
            type_ = 'UMFUTURE_MAIN'

        path = 'sapi/v1/asset/transfer'
        param = {
            'type': type_,
            'asset': asset.upper(),
            'amount': amount,
        }
        req = build_request(rest_host, 'POST', path, self.api_key, self.secret_key, param)
        resp = self.requesting.request(req)
        return resp.json()

    def get_trade_fee(self):
        path = 'sapi/v1/asset/tradeFee'
        req = build_request(rest_host, 'GET', path, self.api_key, self.secret_key)

        resp = self.requesting.request(req)
        data = resp.json()
        for d in data:
            print(d['symbol'], d['makerCommission'], d['takerCommission'])

    def get_recent_deal(self, symbol, limit=10):
        path = 'api/v3/myTrades'

        param = {
            'symbol': format_symbol(symbol, True),
            'limit': limit
        }
        req = build_request(rest_host, 'GET', path, self.api_key, self.secret_key, param)
        resp = self.requesting.request(req)
        return resp.json()


class RestExecutor:
    def __init__(self, api):
        self.api = api

    def place_order(self, order):
        api = self.api
        order = prepare_placing(order, api._create_cid())

        path = 'api/v3/order'
        param = {
            'symbol': format_symbol(order.symbol, True),
            'side': order.side.upper(),
            'price': avoid_sci_num(order.price),
            'quantity': order.amount,
            'type': type_map[order.order_type],
            'newClientOrderId': order.client_id,
        }

        if order.order_type == OrderType.Limit:
            param['timeInForce'] = 'GTC'
        elif order.order_type == OrderType.Market:
            del param['price']

        req = build_request(rest_host, 'POST', path, api.api_key, api.secret_key, param)
        api.account.order_processor.process_place_operation(order)
        api.requesting.request_async(order, req, self._on_place)
        return order.client_id

    def cancel_order(self, order):
        api = self.api
        order = prepare_canceling(order)
        client_id = order.client_id

        if client_id is None:
            return logging.error('cancel order without cid: {}'.format(order))

        path = 'api/v3/order'
        param = {
            'symbol': format_symbol(order.symbol, True),
            'origClientOrderId': client_id,
        }

        req = build_request(rest_host, 'DELETE', path, api.api_key, api.secret_key, param)
        api.account.order_processor.process_cancel_operation(order)
        api.requesting.request_async(order, req, self._on_cancel)

    def modify_order(self, old, new, **options):
        api = self.api
        old = old.copy()
        new = prepare_modifying(new, api._create_cid())

        path = 'api/v3/order/cancelReplace'
        param = {
            'symbol': format_symbol(new.symbol, True),
            'side': new.side.upper(),
            'price': avoid_sci_num(new.price),
            'quantity': new.orig_amount,
            'type': type_map[new.order_type],
            'cancelOrigClientOrderId': old.client_id,
            'newClientOrderId': new.client_id,
            'cancelReplaceMode': 'ALLOW_FAILURE',
        }

        if new.order_type == OrderType.Limit:
            param['timeInForce'] = 'GTC'
        elif new.order_type == OrderType.Market:
            del param['price']

        req = build_request(rest_host, 'POST', path, api.api_key, api.secret_key, param)
        api.account.order_processor.process_cancel_operation(old)
        api.account.order_processor.process_place_operation(new)
        api.requesting.request_async((old, new), req, self._on_modify)
        return new.client_id

    def _on_place(self, order, response):
        api = self.api
        order = order.copy()

        if int(response.status_code) // 100 != 2:
            api._fail_request('place_order', order, response)
            order.status = OrderStatus.PlaceFailed
        else:
            j = response.json()
            order.order_id = j['orderId']
            order.status = OrderStatus.Pending

            if order.order_type == OrderType.Market:
                order.status = OrderStatus.FullyFilled

        order = api.account.order_processor.update_order(order)
        api._put_data(UserEvent.Order, order)

    def _on_cancel(self, order, response):
        api = self.api
        order = order.copy()

        if int(response.status_code) // 100 != 2:
            api._fail_request('cancel_order', order, response)

        if int(response.status_code) == -1:
            order.status = OrderStatus.CancelFailed
        else:
            order.status = OrderStatus.Canceled

        order = api.account.order_processor.update_order(order)
        api._put_data(UserEvent.Order, order)

    def _on_modify(self, replace, response):
        old, new = replace
        old = old.copy()
        new = new.copy()
        j = response.json()

        send_fail = int(response.status_code) == -1
        send_not_valid = 'code' in j and 'data' not in j

        if send_fail or send_not_valid:
            api._fail_request('modify', replace, response)

            old.status = OrderStatus.CancelFailed
            old = api.account.order_processor.update_order(old)
            api._put_data(UserEvent.Order, old)

            new.status = OrderStatus.PlaceFailed
            new = api.account.order_processor.update_order(new)
            api._put_data(UserEvent.Order, new)
            return

        if 'cancelResult' not in j or j['cancelResult'] != 'SUCCESS':
            if j['data']['cancelResult'] != 'SUCCESS':
                api._fail_request('modify_cancel', old, response)

        old.status = OrderStatus.Canceled
        old = api.account.order_processor.update_order(old)
        api._put_data(UserEvent.Order, old)

        if 'data' in j:
            new_order_resp = j['data']['newOrderResponse']
        else:
            new_order_resp = j['newOrderResponse']

        if 'code' in new_order_resp and 'clientOrderId' not in new_order_resp:
            api._fail_request('place_order', new, response)
            new.status = OrderStatus.PlaceFailed
        else:
            new.order_id = new_order_resp['orderId']
            new.status = OrderStatus.Pending

            if new.order_type == OrderType.Market:
                new.status = OrderStatus.FullyFilled

            if 'origQty' in new_order_resp:
                new.orig_amount = abs(float(new_order_resp['origQty']))

        new = api.account.order_processor.update_order(new)
        api._put_data(UserEvent.Order, new)


class WsExecutor:
    def __init__(self, api):
        self.api = api
        self.socket = Socket(ws_executor_host, self._on_open, self._on_message, lambda x: None)
        self.socket.connect()

        self._msg_head = next_cid_head()
        self._msg_iota = Iota()
        self._placing = {}  # msg_id: (ts, order)
        self._canceling = {}  # msg_id: (ts, order)
        self._modifying = {}  # msg_id: (ts, (old, new))
        Timer2(self._kill_timeout, 1).start()

    def place_order(self, order):
        api = self.api
        msg_id = '{}{}'.format(self._msg_head, self._msg_iota.next())
        order = prepare_placing(order, api._create_cid())
        self._placing[msg_id] = (time.time(), order)

        param = {
            'symbol': format_symbol(order.symbol, True),
            'side': order.side.upper(),
            'price': avoid_sci_num(order.price),
            'quantity': order.amount,
            'type': type_map[order.order_type],
            'newClientOrderId': order.client_id,
        }

        if order.order_type == OrderType.Limit:
            param['timeInForce'] = 'GTC'
        elif order.order_type == OrderType.Market:
            del param['price']

        param = sign_ws_param(param, self.api.api_key, self.api.secret_key)
        data = {"id": msg_id, "method": "order.place", "params": param}
        msg = json.dumps(data)

        api.account.order_processor.process_place_operation(order)
        self.socket.assert_send(msg)
        return order.client_id

    def cancel_order(self, order):
        api = self.api
        msg_id = '{}{}'.format(self._msg_head, self._msg_iota.next())
        order = prepare_canceling(order)
        self._canceling[msg_id] = (time.time(), order)

        client_id = order.client_id
        if client_id is None:
            return logging.error('cancel order without cid: {}'.format(order))

        params = {
            'symbol': format_symbol(order.symbol, True),
            'origClientOrderId': client_id,
        }
        params = sign_ws_param(params, self.api.api_key, self.api.secret_key)
        data = {"id": msg_id, "method": "order.cancel", "params": params}

        msg = json.dumps(data)
        api.account.order_processor.process_cancel_operation(order)
        self.socket.assert_send(msg)

    def modify_order(self, old, new, **options):
        api = self.api
        old = old.copy()
        new = prepare_modifying(new, api._create_cid())

        msg_id = '{}{}'.format(self._msg_head, self._msg_iota.next())
        self._modifying[msg_id] = (time.time(), (old, new))

        param = {
            'symbol': format_symbol(new.symbol, True),
            'side': new.side.upper(),
            'price': avoid_sci_num(new.price),
            'quantity': new.orig_amount,
            'type': type_map[new.order_type],
            'cancelOrigClientOrderId': old.client_id,
            'newClientOrderId': new.client_id,
            'cancelReplaceMode': 'ALLOW_FAILURE',
        }

        if new.order_type == OrderType.Limit:
            param['timeInForce'] = 'GTC'
        elif new.order_type == OrderType.Market:
            del param['price']

        param = sign_ws_param(param, self.api.api_key, self.api.secret_key)
        data = {"id": msg_id, "method": "order.cancelReplace", "params": param}
        msg = json.dumps(data)

        api.account.order_processor.process_cancel_operation(old)
        api.account.order_processor.process_place_operation(new)
        self.socket.assert_send(msg)
        return new.client_id

    def _on_place(self, data):
        id_ = data['id']
        order = self._placing.pop(id_)[1]
        order = order.copy()
        status_code = data['status']
        api = self.api

        if int(status_code) // 100 != 2:
            response = self._build_fail_response(status_code, str(data))
            api._fail_request('place_order', order, response)
            order.status = OrderStatus.PlaceFailed
        else:
            order.order_id = data['result']['orderId']
            order.status = OrderStatus.Pending

            if order.order_type == OrderType.Market:
                order.status = OrderStatus.FullyFilled

        order = api.account.order_processor.update_order(order)
        api._put_data(UserEvent.Order, order)

    def _on_cancel(self, data):
        id_ = data['id']
        order = self._canceling.pop(id_)[1]
        order = order.copy()
        status_code = data['status']
        api = self.api

        if int(status_code) // 100 != 2:
            response = self._build_fail_response(status_code, str(data))
            api._fail_request('cancel_order', order, response)

        order.status = OrderStatus.Canceled
        order = api.account.order_processor.update_order(order)
        api._put_data(UserEvent.Order, order)

    def _on_modify(self, data):
        id_ = data['id']
        old, new = self._modifying.pop(id_)[1]

        old = old.copy()
        new = new.copy()

        status_code = data['status']
        if status_code == 200:
            j = data['result']
        else:
            j = data['error']

        if status_code != 200 and 'data' not in j:
            self._modify_send_fail(old, new, str(j))
            return

        if 'cancelResult' not in j or j['cancelResult'] != 'SUCCESS':
            if j['data']['cancelResult'] != 'SUCCESS':
                fail = j['data']['cancelResponse']['msg']
                response = self._build_fail_response(status_code, fail)
                api._fail_request('modify_cancel', old, response)

        old.status = OrderStatus.Canceled
        old = api.account.order_processor.update_order(old)
        api._put_data(UserEvent.Order, old)

        if 'data' in j:
            new_order_resp = j['data']['newOrderResponse']
        else:
            new_order_resp = j['newOrderResponse']

        if 'code' in new_order_resp and 'clientOrderId' not in new_order_resp:
            fail = new_order_resp['msg']
            response = self._build_fail_response(status_code, fail)
            api._fail_request('modify_place', new, response)
            new.status = OrderStatus.PlaceFailed
        else:
            new.order_id = new_order_resp['orderId']
            new.status = OrderStatus.Pending

            if new.order_type == OrderType.Market:
                new.status = OrderStatus.FullyFilled

            if 'origQty' in new_order_resp:
                new.orig_amount = abs(float(new_order_resp['origQty']))

        new = api.account.order_processor.update_order(new)
        api._put_data(UserEvent.Order, new)

    def _on_open(self, ws):
        return

    def _on_message(self, ws, message):
        data = json.loads(message)
        id_ = data['id']
        if id_ in self._placing:
            self._on_place(data)
        elif id_ in self._canceling:
            self._on_cancel(data)
        elif id_ in self._modifying:
            self._on_modify(data)
        else:
            logging.warn('WsExe not recorded msg: {}'.format(message))

    def _build_fail_response(self, code, text):
        try:
            content = '"{}"'.format(text)
            content = content.encode()
        except Exception as _:
            err = 'Requesting._build_fail_response() err, cannot encode {}'.format(text)
            logging.error(err)
            content = b'{}'

        resp = requests.Response()
        resp._content = content
        resp.status_code = code
        return resp

    def _kill_timeout(self):
        timeout = config.rest_timeout
        too_late = time.time() - timeout
        placing = self._placing
        canceling = self._canceling
        modifying = self._modifying

        kill_place = [msg_id for msg_id, (ts, order) in placing.items() if ts < too_late]
        kill_cancel = [msg_id for msg_id, (ts, order) in canceling.items() if ts < too_late]
        kill_modify = [msg_id for msg_id, (ts, replace) in modifying.items() if ts < too_late]

        api = self.api
        for msg_id in kill_place:
            ts, order = placing.pop(msg_id)
            order = order.copy()

            response = self._build_fail_response(-1, 'ws_executor timeout')
            api._fail_request('place_order', order, response)

            order.status = OrderStatus.PlaceFailed
            order = api.account.order_processor.update_order(order)
            api._put_data(UserEvent.Order, order)

        for msg_id in kill_cancel:
            ts, order = canceling.pop(msg_id)
            order = order.copy()

            response = self._build_fail_response(-1, 'ws_executor timeout')
            api._fail_request('cancel_order', order, response)

            order.status = OrderStatus.CancelFailed
            order = api.account.order_processor.update_order(order)
            api._put_data(UserEvent.Order, order)

        for msg_id in kill_modify:
            ts, (old, new) = modifying.pop(msg_id)
            old = old.copy()
            new = new.copy()
            self._modify_send_fail(old, new, 'ws_executor timeout')

    def _modify_send_fail(self, old, new, msg):
        api = self.api
        response = self._build_fail_response(-1, msg)
        api._fail_request('modify', (old, new), response)
        old.status = OrderStatus.CancelFailed
        new.status = OrderStatus.PlaceFailed

        old = api.account.order_processor.update_order(old)
        new = api.account.order_processor.update_order(new)
        api._put_data(UserEvent.Order, old)
        api._put_data(UserEvent.Order, new)


class BinanceSpotSpi(SpiBasic):
    _listen_key = ''
    _extend_key_started = False

    def add_symbol(self, symbol):
        super().add_symbol(symbol)
        self.connect_once()
        if not self._extend_key_started:
            Timer2(self._extend_listen_key, extend_listen_key_interval).start()
            self._extend_key_started = True

    def _create_socket(self):
        socket = Socket('', self._on_open, self._on_message, self._on_close, self._pre_connect)
        return socket

    def _pre_connect(self, ws):
        listen_key = None
        while listen_key is None:
            try:
                listen_key = get_listen_key(self.api_key, self.secret_key)
                self._listen_key = listen_key
            except:
                catch_exception()
            if listen_key is None:
                time.sleep(1)

        host = '{}/{}'.format(ws_host, listen_key)
        self._socket.set_host(host)

    def _extend_listen_key(self):
        try:
            result = extend_listen_key(self.api_key, self.secret_key, self._listen_key)
        except:
            catch_exception()

    def _on_listen_key_expire(self):  # 似乎用不上。因为没有expire回报
        logging.warn('BinanceSpotSpi listen key expired')
        self._socket.reconnect()

    def _on_message(self, ws, message):
        j = json.loads(message)
        e = j['e']
        if e == 'outboundAccountPosition':
            self._handle_balance(j)
        elif e == 'executionReport':
            self._handle_order(j)
        elif e == 'listenKeyExpired':  # 现货似乎没有expire回报。先留着吧
            self._on_listen_key_expire()
        else:
            logging.warn('BinanceSpotSpi unknown msg:{}'.format(message))

    def _handle_balance(self, data):
        balance = self.account.balance
        for dic in data['B']:
            asset = dic['a'].lower()
            free = float(dic['f'])
            frozen = float(dic['l'])
            update_balance_default(balance, asset, free, frozen)
        self._put_data(UserEvent.Balance, balance)

    def _handle_order(self, data):
        cid = data['C']
        if cid == '':
            cid = data['c']

        symbol = resume_symbol(data['s'])
        if symbol not in self._symbol_added:
            return

        order = Order(
            symbol=symbol,
            side=data['S'].lower(),
            price=float(data['p']),
            amount=abs(float(data['q'])) - float(data['z']),
            order_id=data['i'],
            client_id=cid,
            status=status_map[data['X']]
        )
        order.orig_amount = abs(float(data['q']))
        order = self.account.order_processor.update_order(order)
        self._put_data(UserEvent.Order, order)

        deal = float(data['l'])
        if deal != 0:
            self._put_deal(order, deal)


class BinanceSpotFunctions(Functions):
    def get_all_ticker(self):
        url = 'https://api.binance.com/api/v3/ticker/24hr'
        response = self.session.get(url, timeout=3)
        if int(response.status_code) // 100 != 2:
            logging.warn('Binance func-api error: {}'.format(response.text))
            return {}
        j = response.json()

        result = {}
        _usdt = get_asset_value('usdt')
        for d in j:
            symbol = d['symbol'].lower()
            money = symbol[-4:]
            if money != 'usdt' and money != 'busd':
                continue
            symbol = '{}/{}'.format(symbol[:-4], symbol[-4:])

            bid = float(d['bidPrice'])
            if bid == 0:
                continue

            result[symbol] = {
                'price': float(d['lastPrice']),
                'vol': float(d['quoteVolume']) * _usdt,
                'value': float(d['lastPrice']) * _usdt,
                'bid': bid,
                'ask': float(d['askPrice']),
            }

        return result

    def get_all_precision(self):
        url = 'https://www.binance.com/api/v3/exchangeInfo'
        response = self.session.get(url, timeout=3)
        j = response.json()
        if int(response.status_code) // 100 != 2:
            logging.warn('Binance func-api error: {}'.format(response.text))
            return {}

        result = {}
        for d in j['symbols']:
            contract = d['symbol']
            if not (contract.endswith('USDT') or contract.endswith('BUSD')):
                continue

            syb = '{}/{}'.format(contract[:-4], contract[-4:]).lower()
            price_unit = None
            amount_unit = None
            for _filter in d['filters']:
                if _filter['filterType'] == 'PRICE_FILTER':
                    price_unit = _filter['tickSize']
                if _filter['filterType'] == 'LOT_SIZE':
                    amount_unit = _filter['stepSize']

            price_unit = price_unit.rstrip('0')
            amount_unit = amount_unit.rstrip('0')

            unit = (price_unit, amount_unit)
            if None in unit:
                print('None in presicion unit:', d['filters'])
                continue

            result[syb] = unit
        return result

    def get_value_factor(self, symbol):
        return spot_value_factor(symbol)

    def get_all_contract_size(self):
        all_ticker = self.get_all_ticker()
        result = {s: 1 for s in all_ticker}
        return result

    def get_recent_trade(self, symbol):
        url = 'https://api.binance.com/api/v3/aggTrades'
        param = {
            'symbol': symbol.replace('/', '').upper(),
            'limit': 1000
        }
        response = self.session.get(url, timeout=3, params=param)

        if int(response.status_code) // 100 != 2:
            logging.warn('Binance func-api error: {}'.format(response.text))
            return []
        j = response.json()

        result = [
            (
                d['f'],
                'sell' if d['m'] is True else 'buy',
                float(d['p']),
                float(d['q']),
                d['T'] / 1000,
            )
            for d in j
        ]
        return result

    def simulate_deal(self, order, deal_amount, mid_price, account):
        return simulate_deal_spot(order, deal_amount, mid_price, account)


def format_symbol(symbol, upper=False):
    result = symbol.replace('/', '')
    if upper:
        result = result.upper()
    return result


def resume_symbol(contract: str):
    contract = contract.lower()
    if contract.endswith('usdt'):
        return contract[:-4] + '/usdt'
    if contract.endswith('busd'):
        return contract[:-4] + '/busd'
    if contract.endswith('btc'):
        return contract[:-4] + '/btc'
    raise NotImplementedError('Binance resume symbol fail for {}'.format(contract))


def book_host(symbol, frequency):
    symbol = format_symbol(symbol)
    if frequency == DataFrequency.Low:
        freq = '1000ms'
    elif frequency == DataFrequency.Normal:
        freq = '100ms'
    else:
        freq = '100ms'
    result = '{}/{}@depth@{}'.format(ws_host, symbol, freq)
    return result


def trade_host(symbol, frequency):
    symbol = format_symbol(symbol)
    if frequency == DataFrequency.Low:
        freq = 'aggTrade'
    elif frequency == DataFrequency.Normal:
        freq = 'aggTrade'
    else:
        freq = 'aggTrade'
    result = '{}/{}@{}'.format(ws_host, symbol, freq)
    return result


def ticker_host(symbol, frequency):
    symbol = format_symbol(symbol)
    result = '{}/{}@bookTicker'.format(ws_host, symbol)
    return result


def get_listen_key(api_key, secret_key):
    path = 'api/v3/userDataStream'
    req = build_request(rest_host, 'POST', path, api_key, secret_key)
    req.url = req.url.split('?')[0]  # 大无语, 不需要加密

    result = requests.post(req.url, headers=req.headers, timeout=5)
    return result.json()['listenKey']


def extend_listen_key(api_key, secret_key, listen_key):
    path = 'api/v3/userDataStream'
    req = build_request(rest_host, 'PUT', path, api_key, secret_key)
    req.url = req.url.split('?')[0] + '?listenKey={}'.format(listen_key)

    result = requests.put(req.url, headers=req.headers, timeout=5)
    return result.json()


rest_host = 'https://api.binance.com'
ws_host = 'wss://stream.binance.com:9443/ws'
ws_executor_host = 'wss://ws-api.binance.com:443/ws-api/v3'
side_map = {True: 'sell', False: 'buy'}
type_map = {
    OrderType.Limit: 'LIMIT',
    OrderType.Market: 'MARKET',
    OrderType.PostOnly: 'LIMIT_MAKER'
}
status_map = {
    'NEW':              OrderStatus.Pending,
    'PARTIALLY_FILLED': OrderStatus.PartFilled,
    'FILLED':           OrderStatus.FullyFilled,
    'CANCELED':         OrderStatus.Canceled,
    'PENDING_CANCEL':   OrderStatus.Canceled,
    'REJECTED':         OrderStatus.PlaceFailed,
    'EXPIRED':          OrderStatus.Canceled,
}
extend_listen_key_interval = 600


if __name__ == '__main__':
    from quant.utils import set_test_mode
    set_test_mode()

    import keys

    api = BinanceSpotApi(keys.get_key('bn_j1'))
    # api.set_ws_mode(True)
    # print(api.get_trade_fee())
    # print(api.transfer_swap(100, asset='busd'))
    # input(':')

    ee = api.account.info_engine
    ee.show_user_data()
    ee.show_problems()

    def on_place(order):
        print('placing', order)
    # ee.subscribe(ee.Place, on_place)

    def on_cancel(order):
        print('canceling', order)
    # ee.subscribe(ee.Cancel, on_cancel)

    a = Order('btc/usdt', 'buy', 1, 1, 'qu1')
    b = Order('btc/usdt', 'buy', 25001, 0.1, order_type=OrderType.PostOnly)
    cid = api.modify_order(a, b)
    api.join()

    input(':')

    # api.query_all_open_orders()
    # api.join()






























